1use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
26use crate::copp::copp2::formulation::Copp2Problem;
27use crate::copp::copp2::opt2::ClarabelExpertInfor2nd;
28use crate::copp::copp2::opt2::clarabel_constraints::{
29 clarabel_standard_capacity_topp2, clarabel_standard_constraint_topp2,
30};
31use crate::copp::{ClarabelOptions, CoppObjective, clarabel_to_copp2_solution};
32use crate::diag::{
33 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
34 format_duration_human,
35};
36use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
37use clarabel::algebra::CscMatrix;
38use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
39use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
40use core::f64;
41use itertools::{Itertools, izip};
42use nalgebra::{DMatrix, DVectorView};
43
44#[cfg(any(feature = "c", feature = "python", test))]
45use crate::copp::copp2::stable::basic::a_to_b_topp2;
46
47pub fn copp2_socp<'a, M: RobotTorque>(
68 problem: &Copp2Problem<'a, M>,
69 options: &ClarabelOptions,
70) -> Result<Vec<f64>, CoppError> {
71 let (a_profile, solution) = copp2_socp_expert(problem, options)?;
72 a_profile.ok_or_else(|| CoppError::ClarabelSolverStatus("copp2_socp".into(), solution.status))
73}
74
75pub fn copp2_socp_expert<'a, M: RobotTorque>(
106 problem: &Copp2Problem<'a, M>,
107 options: &ClarabelOptions,
108) -> Result<(Option<Vec<f64>>, DefaultSolution<f64>), CoppError> {
109 let result = copp2_socp_expert_with_info(problem, options)?;
110 Ok((result.result, result.solution))
111}
112
113pub fn copp2_socp_expert_with_info<'a, M: RobotTorque>(
123 problem: &Copp2Problem<'a, M>,
124 options: &ClarabelOptions,
125) -> Result<ClarabelExpertInfor2nd, CoppError> {
126 match options.verbosity() {
127 Verbosity::Silent => copp2_socp_core(problem, (options, SilentVerboser)),
128 Verbosity::Summary => copp2_socp_core(problem, (options, SummaryVerboser::new())),
129 Verbosity::Debug => copp2_socp_core(problem, (options, DebugVerboser::new())),
130 Verbosity::Trace => copp2_socp_core(problem, (options, TraceVerboser::new())),
131 }
132}
133
134fn copp2_socp_core<'a, M: RobotTorque>(
146 problem: &Copp2Problem<'a, M>,
147 options_verboser: (&ClarabelOptions, impl Verboser),
148) -> Result<ClarabelExpertInfor2nd, CoppError> {
149 let (options, mut verboser) = options_verboser;
150 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
151 if verboser.is_enabled(Verbosity::Summary) {
152 verboser.record_start_time();
153 crate::verbosity_log!(
154 crate::diag::Verbosity::Summary,
155 "\ncopp2_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
156 idx_s_start,
157 idx_s_final,
158 problem.objectives.len(),
159 problem.s_len()
160 );
161 }
162 if verboser.is_enabled(Verbosity::Trace) {
163 let settings = options.clarabel_settings();
164 crate::verbosity_log!(
165 crate::diag::Verbosity::Summary,
166 "copp2_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
167 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
168 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
169 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
170 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
171 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
172 settings.tol_gap_rel,
173 settings.tol_feas,
174 settings.max_iter,
175 settings.verbose
176 );
177 }
178 let n = idx_s_final - idx_s_start;
180 let (cap_val_std, cap_b_std, cap_cone_std) =
184 clarabel_standard_capacity_topp2(&problem.robot.constraints, problem.idx_s_interval);
185 let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
186 clarabel_objective_capacity_copp2(n, problem.objectives, problem.robot);
187 if verboser.is_enabled(Verbosity::Debug) {
188 crate::verbosity_log!(
189 crate::diag::Verbosity::Summary,
190 "copp2_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
191 );
192 }
193 let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
196 let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
197 let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
198 let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
199 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
200 if verboser.is_enabled(Verbosity::Trace) {
201 crate::verbosity_log!(
202 crate::diag::Verbosity::Summary,
203 "copp2_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
204 cap_val_std + cap_val_obj,
205 cap_val_std + cap_val_obj,
206 cap_val_std + cap_val_obj,
207 cap_b_std + cap_b_obj,
208 cap_cone_std + cap_cone_obj
209 );
210 }
211 let row_before_std = row.len();
213 let col_before_std = col.len();
214 let val_before_std = val.len();
215 let b_before_std = b.len();
216 let cones_before_std = cones.len();
217 clarabel_standard_constraint_topp2(
218 &problem.as_topp2_problem(),
219 (&mut row, &mut col, &mut val, &mut b, &mut cones),
220 &verboser,
221 );
222 if verboser.is_enabled(Verbosity::Trace) {
223 crate::verbosity_log!(
224 crate::diag::Verbosity::Summary,
225 "copp2_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
226 row.len() - row_before_std,
227 col.len() - col_before_std,
228 val.len() - val_before_std,
229 b.len() - b_before_std,
230 cones.len() - cones_before_std
231 );
232 }
233 let row_before_sqrt = row.len();
236 let col_before_sqrt = col.len();
237 let val_before_sqrt = val.len();
238 let b_before_sqrt = b.len();
239 let cones_before_sqrt = cones.len();
240 let n_var_old = clarabel_sqrt_a_copp2(
241 n,
242 problem.objectives,
243 (&mut row, &mut col, &mut val, &mut b, &mut cones),
244 );
245 if verboser.is_enabled(Verbosity::Trace) {
246 crate::verbosity_log!(
247 crate::diag::Verbosity::Summary,
248 "copp2_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
249 row.len() - row_before_sqrt,
250 col.len() - col_before_sqrt,
251 val.len() - val_before_sqrt,
252 b.len() - b_before_sqrt,
253 cones.len() - cones_before_sqrt,
254 n_var_old
255 );
256 }
257 let mut q_object = Vec::<f64>::with_capacity(n_vars);
258 q_object.resize(n_var_old, 0.0);
259 let row_before_obj = row.len();
261 let col_before_obj = col.len();
262 let val_before_obj = val.len();
263 let b_before_obj = b.len();
264 let cones_before_obj = cones.len();
265 let q_before_obj = q_object.len();
266 clarable_objective_copp2(
267 problem,
268 (
269 &mut row,
270 &mut col,
271 &mut val,
272 &mut b,
273 &mut cones,
274 &mut q_object,
275 ),
276 )?;
277 if verboser.is_enabled(Verbosity::Trace) {
278 let (q_min, q_max) = q_object
279 .iter()
280 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
281 (mn.min(v), mx.max(v))
282 });
283 crate::verbosity_log!(
284 crate::diag::Verbosity::Summary,
285 "copp2_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
286 row.len() - row_before_obj,
287 col.len() - col_before_obj,
288 val.len() - val_before_obj,
289 b.len() - b_before_obj,
290 cones.len() - cones_before_obj,
291 q_object.len() - q_before_obj,
292 q_min,
293 q_max
294 );
295 }
296 if verboser.is_enabled(Verbosity::Debug) {
297 crate::verbosity_log!(
298 crate::diag::Verbosity::Summary,
299 "copp2_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
300 row.len(),
301 col.len(),
302 val.len(),
303 b.len(),
304 cones.len(),
305 q_object.len()
306 );
307 }
308 if verboser.is_enabled(Verbosity::Summary) {
309 crate::verbosity_log!(
310 crate::diag::Verbosity::Summary,
311 "copp2_socp: ready to solve with row/col/val/b/cones = {}/{}/{}/{}/{} and n_var = {}.",
312 row.len(),
313 col.len(),
314 val.len(),
315 b.len(),
316 cones.len(),
317 q_object.len()
318 );
319 }
320 let n_var = q_object.len();
322 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
323 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
324 if verboser.is_enabled(Verbosity::Trace) {
325 crate::verbosity_log!(
326 crate::diag::Verbosity::Summary,
327 "copp2_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
328 b.len(),
329 n_var,
330 a_csc.nnz(),
331 p_object.nnz()
332 );
333 }
334 let settings = options.clarabel_settings().clone();
336 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
337 .map_err(|e| CoppError::ClarabelSolverError("copp2_socp".into(), e))?;
338 solver.solve();
339 let linsolver = solver.info.linsolver.clone();
340 let solution = solver.solution;
341 if verboser.is_enabled(Verbosity::Summary) {
342 crate::verbosity_log!(
343 crate::diag::Verbosity::Summary,
344 "copp2_socp: solve done, status = {:?}, elapsed = {}.",
345 solution.status,
346 format_duration_human(verboser.elapsed())
347 );
348 }
349 if verboser.is_enabled(Verbosity::Trace) {
350 let show = solution.x.len().min(3);
351 crate::verbosity_log!(
352 crate::diag::Verbosity::Summary,
353 "copp2_socp: solution x_len={}, head={:?}",
354 solution.x.len(),
355 &solution.x[0..show]
356 );
357 }
358 let a_profile = if options.is_allow(solution.status) {
359 Some(clarabel_to_copp2_solution(problem.s_len(), &solution))
360 } else {
361 None
362 };
363 if verboser.is_enabled(Verbosity::Trace) {
364 crate::verbosity_log!(
365 crate::diag::Verbosity::Summary,
366 "copp2_socp: allow(status)={}, extracted_profile={}",
367 options.is_allow(solution.status),
368 if a_profile.is_some() {
369 "Some(a)"
370 } else {
371 "None"
372 }
373 );
374 }
375 Ok(ClarabelExpertInfor2nd {
376 result: a_profile,
377 solution,
378 linsolver,
379 })
380}
381
382fn clarabel_objective_capacity_copp2<M: RobotBasic>(
384 n: usize,
385 objective: &[CoppObjective],
386 robot: &Robot<M>,
387) -> (usize, usize, usize, usize) {
388 let flag_need_eta = objective.iter().any(|obj| {
389 matches!(
390 obj,
391 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
392 )
393 });
394 let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) = if flag_need_eta {
397 (4 * (n + 1), 4 * (n + 1), n + 2, 2 * (n + 1))
398 } else {
399 (0, 0, 0, n + 1)
400 };
401 let dim = robot.dim();
403 for obj in objective {
404 match obj {
405 CoppObjective::Time(_) => {
406 capacity_val += 6 * n;
408 capacity_b += 3 * n;
409 capacity_cones += n;
410 n_vars += n;
411 }
412 CoppObjective::ThermalEnergy(_, _) => {
413 capacity_val += (6 + 2 * dim) * n;
415 capacity_b += (dim + 2) * n;
416 capacity_cones += n;
417 n_vars += n;
418 }
419 CoppObjective::TotalVariationTorque(_, _) => {
420 capacity_val += 8 * dim * n;
422 capacity_b += 2 * dim * n;
423 capacity_cones += 1;
424 n_vars += dim * n;
425 }
426 _ => {}
427 }
428 }
429 (capacity_val, capacity_b, capacity_cones, n_vars)
430}
431
432fn clarabel_sqrt_a_copp2(
438 n: usize,
439 objective: &[CoppObjective],
440 constraints: ConstraintsClarabel,
441) -> usize {
442 let (row, col, val, b, cones) = constraints;
443 for obj in objective {
444 match obj {
445 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
446 row.extend(b.len()..b.len() + n + 1);
449 col.extend((n + 1)..(2 * (n + 1)));
450 val.resize(val.len() + n + 1, -1.0);
451 b.resize(b.len() + n + 1, 0.0);
452 cones.push(NonnegativeConeT(n + 1));
453 row.extend(b.len()..b.len() + 3 * (n + 1));
458 val.resize(val.len() + 3 * (n + 1), -1.0);
459 cones.resize(cones.len() + n + 1, SecondOrderConeT(3));
460 for k in 0..=n {
461 col.extend([k, k, k + n + 1]);
462 b.extend([0.25, -0.25, 0.0]);
463 }
464 return 2 * (n + 1);
465 }
466 _ => {}
467 }
468 }
469 n + 1
470}
471
472fn clarabel_objective_time_copp2(
475 s: &[f64],
476 weight: f64,
477 objective_constraints: ObjConsClarabel,
478) -> bool {
479 if weight < 0.0 {
480 return false;
481 }
482 let (row, col, val, b, cones, q_object) = objective_constraints;
483 let weight = 8.0 * weight;
487 let n_var_old = q_object.len();
488 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
490 let len = s.len();
494 for k in 0..(len - 1) {
495 row.resize(row.len() + 3, b.len());
497 col.extend([len + k, len + k + 1, n_var_old + k]);
498 val.extend([-1.0, -1.0, -1.0]);
499 b.push(0.0);
500 row.resize(row.len() + 3, b.len());
502 col.extend([len + k, len + k + 1, n_var_old + k]);
503 val.extend([-1.0, -1.0, 1.0]);
504 b.push(0.0);
505 b.push(1.0);
507 }
508 cones.resize(cones.len() + len - 1, SecondOrderConeT(3));
509 true
510}
511
512fn clarabel_objective_thermal_energy_copp2(
515 s: &[f64],
516 weight: f64,
517 normalize: &[f64],
518 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
519 objective_constraints: ObjConsClarabel,
520) -> bool {
521 if weight < 0.0 {
522 return false;
523 }
524 let (row, col, val, b, cones, q_object) = objective_constraints;
525 let len = s.len();
529 let mut coeff_a_curr = coeffs_torque.0.clone();
530 let mut coeff_a_next = coeffs_torque.1.clone();
531 let mut coeff_g = coeffs_torque.2.clone();
532 let weight = 8.0 * weight;
534 let n_var_old = q_object.len();
535 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
537 let dim = coeff_a_curr.nrows();
539 if normalize.len() != dim {
541 return false;
542 }
543 let normalize = DVectorView::from_slice(normalize, dim);
544 for mut col in coeff_a_curr.column_iter_mut() {
545 col.component_mul_assign(&normalize);
546 }
547 for mut col in coeff_a_next.column_iter_mut() {
548 col.component_mul_assign(&normalize);
549 }
550 for mut col in coeff_g.column_iter_mut() {
551 col.component_mul_assign(&normalize);
552 }
553 for (k, (col_a_curr, col_a_next, col_g)) in izip!(
558 coeff_a_curr.column_iter(),
559 coeff_a_next.column_iter(),
560 coeff_g.column_iter()
561 )
562 .enumerate()
563 {
564 row.resize(row.len() + 3, b.len());
566 col.extend([len + k, len + k + 1, n_var_old + k]);
567 val.extend([-1.0, -1.0, -1.0]);
568 b.push(0.0);
569 row.resize(row.len() + 3, b.len());
571 col.extend([len + k, len + k + 1, n_var_old + k]);
572 val.extend([-1.0, -1.0, 1.0]);
573 b.push(0.0);
574 for (&v_a_curr, &v_a_next, &v_g) in
576 izip!(col_a_curr.iter(), col_a_next.iter(), col_g.iter())
577 {
578 row.resize(row.len() + 2, b.len());
579 col.extend([k, k + 1]);
580 val.extend([v_a_curr, v_a_next]);
581 b.push(v_g);
582 }
583 }
584 cones.resize(cones.len() + len - 1, SecondOrderConeT(dim + 2));
585 true
586}
587
588fn clarabel_objective_tv_torque_copp2(
591 weight: f64,
592 normalize: &[f64],
593 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
594 objective_constraints: ObjConsClarabel,
595) -> bool {
596 if weight < 0.0 {
597 return false;
598 }
599 let (row, col, val, b, cones, q_object) = objective_constraints;
600 let mut coeff_a_curr = coeffs_torque.0.clone();
603 let mut coeff_a_next = coeffs_torque.1.clone();
604 let mut coeff_g = coeffs_torque.2.clone();
605 let dim = coeff_a_curr.nrows();
606 if normalize.len() != dim {
607 return false;
608 }
609 let normalize = DVectorView::from_slice(normalize, dim);
611 for mut col in coeff_a_curr.column_iter_mut() {
612 col.component_mul_assign(&normalize);
613 }
614 for mut col in coeff_a_next.column_iter_mut() {
615 col.component_mul_assign(&normalize);
616 }
617 for mut col in coeff_g.column_iter_mut() {
618 col.component_mul_assign(&normalize);
619 }
620 let n_b_old = b.len();
625 let mut buffer0 = vec![0.0; dim];
628 let mut buffer1 = vec![0.0; dim];
629 for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
630 coeff_a_curr.column_iter(),
631 coeff_a_next.column_iter(),
632 coeff_g.column_iter()
633 )
634 .tuple_windows()
635 .enumerate()
636 {
637 buffer0.clear();
639 buffer1.clear();
640 buffer0.extend(
641 col_a_next
642 .iter()
643 .zip(col_b_curr.iter())
644 .map(|(&v_a_next, &v_b_curr)| v_b_curr - v_a_next),
645 );
646 buffer1.extend(
647 col_g_curr
648 .iter()
649 .zip(col_g_next.iter())
650 .map(|(&v_g_curr, &v_g_next)| v_g_curr - v_g_next),
651 );
652 let n_var_old = q_object.len();
655 for (i, (&v0, &v1, &v_a_curr, &v_b_next)) in izip!(
656 buffer0.iter(),
657 buffer1.iter(),
658 col_a_curr.iter(),
659 col_b_next.iter()
660 )
661 .enumerate()
662 {
663 row.resize(row.len() + 4, b.len());
666 col.extend([k, k + 1, k + 2, n_var_old + i]);
667 val.extend([-v_a_curr, v0, v_b_next, -1.0]);
668 b.push(v1);
669
670 row.resize(row.len() + 4, b.len());
673 col.extend([k, k + 1, k + 2, n_var_old + i]);
674 val.extend([v_a_curr, -v0, -v_b_next, -1.0]);
675 b.push(-v1);
676 }
677
678 q_object.resize(q_object.len() + dim, weight);
680 }
681 cones.push(NonnegativeConeT(b.len() - n_b_old));
682 true
683}
684
685fn clarabel_objective_linear_copp2(
687 s: &[f64],
688 weight: f64,
689 alpha: &[f64],
690 beta: &[f64],
691 q_object: &mut [f64],
692) -> bool {
693 if alpha.len() != s.len() || beta.len() != s.len() - 1 {
694 return false;
695 }
696 for (va, q) in alpha.iter().zip(q_object.iter_mut()) {
699 *q += weight * va;
701 }
702 for (s_pair, vb, q_curr) in izip!(s.windows(2), beta.iter(), q_object.iter_mut()) {
703 *q_curr -= 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
705 }
706 for (s_pair, vb, q_next) in izip!(s.windows(2), beta.iter(), q_object.iter_mut().skip(1)) {
707 *q_next += 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
709 }
710 true
711}
712
713fn clarable_objective_copp2<M: RobotTorque>(
714 problem: &Copp2Problem<M>,
715 objective_constraints: ObjConsClarabel,
716) -> Result<(), CoppError> {
717 let (row, col, val, b, cones, q_object) = objective_constraints;
718 let s = problem
719 .robot
720 .constraints
721 .s_vec(problem.idx_s_interval.0, problem.idx_s_interval.1 + 1)?;
722 let coeffs_torque = if problem.objectives.iter().any(|obj| {
723 matches!(
724 obj,
725 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
726 )
727 }) {
728 problem.robot.torque2_coeff_a(
730 problem.idx_s_interval.0,
731 problem.idx_s_interval.1 - problem.idx_s_interval.0,
732 )?
733 } else {
734 (
735 DMatrix::<f64>::zeros(0, 0),
736 DMatrix::<f64>::zeros(0, 0),
737 DMatrix::<f64>::zeros(0, 0),
738 )
739 };
740 for obj in problem.objectives {
741 match obj {
742 CoppObjective::Time(weight) => {
743 if !clarabel_objective_time_copp2(&s, *weight, (row, col, val, b, cones, q_object))
744 {
745 return Err(CoppError::InvalidInput(
746 "copp2_socp".into(),
747 "Invalid Time objective.".into(),
748 ));
749 }
750 }
751 CoppObjective::ThermalEnergy(weight, normalize) => {
752 if !clarabel_objective_thermal_energy_copp2(
753 &s,
754 *weight,
755 normalize,
756 &coeffs_torque,
757 (row, col, val, b, cones, q_object),
758 ) {
759 return Err(CoppError::InvalidInput(
760 "copp2_socp".into(),
761 "Invalid ThermalEnergy objective.".into(),
762 ));
763 }
764 }
765 CoppObjective::TotalVariationTorque(weight, normalize) => {
766 if !clarabel_objective_tv_torque_copp2(
767 *weight,
768 normalize,
769 &coeffs_torque,
770 (row, col, val, b, cones, q_object),
771 ) {
772 return Err(CoppError::InvalidInput(
773 "copp2_socp".into(),
774 "Invalid TotalVariationTorque objective.".into(),
775 ));
776 }
777 }
778 CoppObjective::Linear(weight, alpha, beta) => {
779 if !clarabel_objective_linear_copp2(&s, *weight, alpha, beta, q_object) {
780 return Err(CoppError::InvalidInput(
781 "copp2_socp".into(),
782 "Invalid Linear objective.".into(),
783 ));
784 }
785 }
786 }
787 }
788 Ok(())
789}
790
791#[cfg(any(feature = "c", feature = "python", test))]
793pub(crate) fn objective_value_copp2_opt<M: RobotTorque>(
794 robot: &Robot<M>,
795 start_idx_s: usize,
796 objective: &[CoppObjective],
797 a_profile: &[f64],
798) -> (f64, Vec<f64>) {
799 let Ok(s) = robot
800 .constraints
801 .s_vec(start_idx_s, start_idx_s + a_profile.len())
802 else {
803 return (f64::INFINITY, vec![0.0; objective.len()]);
804 };
805 if a_profile.len() != s.len() {
806 return (f64::INFINITY, vec![0.0; objective.len()]);
807 }
808 let Ok(b_profile) = a_to_b_topp2(&s, a_profile) else {
809 return (f64::INFINITY, vec![0.0; objective.len()]);
810 };
811 let torque = if objective.iter().any(|obj| {
812 matches!(
813 obj,
814 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
815 )
816 }) {
817 let torque_result =
818 robot.get_torque_with_ab(&a_profile[0..a_profile.len() - 1], &b_profile, start_idx_s);
819 match torque_result {
820 Ok(torque) => torque,
821 _ => return (f64::INFINITY, vec![0.0; objective.len()]),
822 }
823 } else {
824 DMatrix::<f64>::zeros(0, 0)
825 };
826 let a_sqrt = if objective.iter().any(|obj| {
827 matches!(
828 obj,
829 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
830 )
831 }) {
832 a_profile.iter().map(|a| a.sqrt()).collect()
833 } else {
834 Vec::new()
835 };
836 let mut obj_val = Vec::with_capacity(objective.len());
837 let mut obj_val_total = 0.0;
838 for obj in objective {
839 match obj {
840 CoppObjective::Time(weight) => {
841 let obj_here = objective_value_time_copp2(&s, &a_sqrt);
842 obj_val.push(obj_here);
843 obj_val_total += weight * obj_here;
844 }
845 CoppObjective::ThermalEnergy(weight, normalize) => {
846 let obj_here =
847 objective_value_thermal_energy_copp2(&s, &a_sqrt, &torque, normalize);
848 obj_val.push(obj_here);
849 obj_val_total += weight * obj_here;
850 }
851 CoppObjective::TotalVariationTorque(weight, normalize) => {
852 let obj_here = objective_value_tv_torque_copp2(&torque, normalize);
853 obj_val.push(obj_here);
854 obj_val_total += weight * obj_here;
855 }
856 CoppObjective::Linear(weight, alpha, beta) => {
857 let obj_here = objective_value_linear_copp2(&s, a_profile, alpha, beta);
858 obj_val.push(obj_here);
859 obj_val_total += weight * obj_here;
860 }
861 }
862 }
863 (obj_val_total, obj_val)
864}
865
866#[cfg(any(feature = "c", feature = "python", test))]
869#[inline(always)]
870fn objective_value_time_copp2(s: &[f64], a_sqrt: &[f64]) -> f64 {
871 let mut objective = 0.0;
873 for (s_pair, a_sqrt_pair) in s.windows(2).zip(a_sqrt.windows(2)) {
874 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]);
875 }
876 2.0 * objective
877}
878
879#[cfg(any(feature = "c", feature = "python", test))]
881#[inline(always)]
882fn objective_value_thermal_energy_copp2(
883 s: &[f64],
884 a_sqrt: &[f64],
885 torque: &DMatrix<f64>,
886 normalize: &[f64],
887) -> f64 {
888 let mut objective = 0.0;
890 for (s_pair, a_sqrt_pair, torque_col) in
891 izip!(s.windows(2), a_sqrt.windows(2), torque.column_iter())
892 {
893 let mut sum = 0.0;
894 for (torque, normal) in torque_col.iter().zip(normalize.iter()) {
895 sum += (torque * normal).powi(2);
896 }
897 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]) * sum;
898 }
899 2.0 * objective
900}
901
902#[cfg(any(feature = "c", feature = "python", test))]
904#[inline(always)]
905fn objective_value_tv_torque_copp2(torque: &DMatrix<f64>, normalize: &[f64]) -> f64 {
906 let mut objective = 0.0;
908 for (torque_col_curr, torque_col_next) in torque.column_iter().tuple_windows() {
909 for (torque_prev, torque_next, normal) in izip!(
910 torque_col_curr.iter(),
911 torque_col_next.iter(),
912 normalize.iter()
913 ) {
914 objective += (torque_next - torque_prev).abs() * normal;
915 }
916 }
917 objective
918}
919
920#[cfg(any(feature = "c", feature = "python", test))]
922#[inline(always)]
923fn objective_value_linear_copp2(s: &[f64], a_profile: &[f64], alpha: &[f64], beta: &[f64]) -> f64 {
924 let mut objective = 0.0;
927 for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
928 objective += a_curr * alpha_curr;
930 }
931 for (a_pair, s_pair, beta_curr) in izip!(a_profile.windows(2), s.windows(2), beta.iter()) {
932 objective += 0.5 * beta_curr * (a_pair[1] - a_pair[0]) / (s_pair[1] - s_pair[0]);
934 }
935 objective
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941 use crate::copp::copp2::stable::basic::{
942 Copp2ProblemBuilder, Topp2ProblemBuilder, s_to_t_topp2,
943 };
944 use crate::copp::copp2::stable::reach_set2::{ReachSet2Options, ReachSet2OptionsBuilder};
945 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
946 use crate::copp::{ClarabelOptions, ClarabelOptionsBuilder};
947 use crate::path::{
948 Path, SplineConfig, add_symmetric_axial_limits_for_test, lissajous_path_for_test,
949 };
950 use crate::robot::demo::Plannar2LinkEnd;
951 use crate::robot::robot_core::Robot;
952 use core::panic;
953 use nalgebra::DMatrix;
954 use std::time::Instant;
955 use std::vec;
956
957 #[test]
958 fn test_copp2_socp_only_time() -> Result<(), CoppError> {
959 run_test_copp2_socp_only_time_repeated(1, false)
960 }
961
962 #[test]
965 #[ignore = "slow"]
966 fn test_copp2_socp_only_time_robust() -> Result<(), CoppError> {
967 run_test_copp2_socp_only_time_repeated(100, true)?;
968 Ok(())
969 }
970
971 #[test]
972 fn test_copp2_socp() -> Result<(), CoppError> {
973 let options_socp = ClarabelOptionsBuilder::new()
974 .allow_almost_solved(true)
975 .build()?;
976 run_test_copp2_socp_once(&options_socp)
977 }
978
979 #[test]
980 #[ignore = "bindings"]
981 fn test_copp2_socp_bindings_parity() -> Result<(), CoppError> {
982 let dim = 3;
983 let num_waypoints = 8;
984 let n: usize = 81;
985 let pi = std::f64::consts::PI;
986
987 let waypoints = DMatrix::<f64>::from_fn(dim, num_waypoints, |axis, j| {
988 let s = j as f64 / (num_waypoints - 1) as f64;
989 match axis {
990 0 => 0.20 * (2.0 * pi * s).sin(),
991 1 => 0.15 * (1.5 * pi * s).cos(),
992 2 => 0.10 * s * (1.0 - s),
993 _ => unreachable!("dimension is fixed to 3"),
994 }
995 });
996 let path = Path::from_waypoints(&waypoints, SplineConfig::default())?;
997 let s = DMatrix::<f64>::from_fn(1, n, |_, j| j as f64 / (n - 1) as f64);
998
999 let mut robot = Robot::with_capacity(dim, n);
1000 robot
1001 .with_s(&s.as_view())?
1002 .with_q_from_path_2nd(&path, 0, n)?;
1003 add_symmetric_axial_limits_for_test(&mut robot, 10.0, 50.0, None)?;
1004 let torque_max = vec![1.0e6; dim];
1005 let torque_min = vec![-1.0e6; dim];
1006 robot.with_axial_torque((torque_max.as_slice(), n), (torque_min.as_slice(), n), 0)?;
1007
1008 let normalize = vec![1.0; dim];
1009 let options = ClarabelOptionsBuilder::new()
1010 .allow_almost_solved(true)
1011 .build()?;
1012
1013 let objectives_thermal = [
1014 CoppObjective::Time(1.0),
1015 CoppObjective::ThermalEnergy(1.0, &normalize),
1016 ];
1017 let problem_thermal =
1018 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_thermal)
1019 .build()?;
1020 let a_thermal = copp2_socp(&problem_thermal, &options)?;
1021 let (t_final_thermal, _) = s_to_t_topp2(s.as_slice(), &a_thermal, 0.0)?;
1022
1023 let objectives_tv = [
1024 CoppObjective::Time(1.0),
1025 CoppObjective::TotalVariationTorque(1.0, &normalize),
1026 ];
1027 let problem_tv =
1028 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_tv).build()?;
1029 let a_tv = copp2_socp(&problem_tv, &options)?;
1030
1031 crate::verbosity_log!(
1032 crate::diag::Verbosity::Summary,
1033 "COPP2-SOCP Rust bindings parity test: t_final_thermal={:.17}, thermal.len={}, tv.len={}",
1034 t_final_thermal,
1035 a_thermal.len(),
1036 a_tv.len()
1037 );
1038
1039 Ok(())
1040 }
1041
1042 #[test]
1050 #[ignore = "slow"]
1051 fn test_copp2_socp_robust() -> Result<(), CoppError> {
1052 run_test_copp2_socp_repeated(100, true)?;
1053 Ok(())
1054 }
1055
1056 fn run_test_copp2_socp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1057 let options_socp = ClarabelOptionsBuilder::new()
1058 .allow_almost_solved(true)
1059 .build()?;
1060
1061 let mut tc_sum_case0 = 0.0;
1062 let mut tc_sum_case1 = 0.0;
1063 let mut tc_sum_case2 = 0.0;
1064 let mut tc_sum_case3 = 0.0;
1065 let mut tc_sum_case4 = 0.0;
1066 let mut obj_sum_case0 = vec![0.0; 4];
1067 let mut obj_sum_case1 = vec![0.0; 4];
1068 let mut obj_sum_case2 = vec![0.0; 4];
1069 let mut obj_sum_case3 = vec![0.0; 4];
1070 let mut obj_sum_case4 = vec![0.0; 4];
1071
1072 for i_exp in 0..n_exp {
1073 let n: usize = 1000;
1074 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1075 let dim = robot.dim();
1076
1077 let mut rng = rand::rng();
1078 let (s, path, omega, phi) =
1079 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1080 robot
1081 .with_s(&s.as_view())?
1082 .with_q_from_path_2nd(&path, 0, n)?;
1083 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1084
1085 let objectives_test = [
1087 CoppObjective::Time(1.0),
1088 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1089 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1090 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1091 ];
1092 let a_feasible = vec![0.0; n];
1093
1094 let mut copp2_problem = Copp2ProblemBuilder::new(
1096 &robot,
1097 (0, n - 1),
1098 (0.0, 0.0),
1099 &[CoppObjective::Time(1.0)],
1100 )
1101 .build()?;
1102 let start = Instant::now();
1103 let mut a_case0 = copp2_socp(&copp2_problem, &options_socp)?;
1104 let tc_copp2_case0 = start.elapsed().as_secs_f64() * 1E3;
1105 robot
1106 .constraints
1107 .project_to_feasible_topp2(&mut a_case0, &a_feasible, 0)?;
1108 let (_, obj_case0) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case0);
1109
1110 let obj_case1 = [
1112 CoppObjective::Time(1.0),
1113 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1114 ];
1115 copp2_problem.objectives = &obj_case1;
1116 let start = Instant::now();
1117 let mut a_case1 = copp2_socp(&copp2_problem, &options_socp)?;
1118 let tc_copp2_case1 = start.elapsed().as_secs_f64() * 1E3;
1119 robot
1120 .constraints
1121 .project_to_feasible_topp2(&mut a_case1, &a_feasible, 0)?;
1122 let (_, obj_case1) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case1);
1123 if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1124 let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0)?;
1125 let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0)?;
1126 crate::verbosity_log!(
1127 crate::diag::Verbosity::Summary,
1128 "omega = {omega:?}\nphi = {phi:?}"
1129 );
1130 crate::verbosity_log!(
1131 crate::diag::Verbosity::Summary,
1132 "Case 0: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1133 obj_case0[0],
1134 obj_case0[1],
1135 tf_case0
1136 );
1137 crate::verbosity_log!(
1138 crate::diag::Verbosity::Summary,
1139 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1140 obj_case1[0],
1141 obj_case1[1],
1142 tf_case1
1143 );
1144 crate::verbosity_log!(
1145 crate::diag::Verbosity::Summary,
1146 "Interesting... Cases 0 and 1"
1147 );
1148 }
1149
1150 let obj_case2 = [
1152 CoppObjective::Time(1.0),
1153 CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1154 ];
1155 copp2_problem.objectives = &obj_case2;
1156 let start = Instant::now();
1157 let mut a_case2 = copp2_socp(&copp2_problem, &options_socp)?;
1158 let tc_copp2_case2 = start.elapsed().as_secs_f64() * 1E3;
1159 robot
1160 .constraints
1161 .project_to_feasible_topp2(&mut a_case2, &a_feasible, 0)?;
1162 let (_, obj_case2) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case2);
1163 if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1164 crate::verbosity_log!(
1165 crate::diag::Verbosity::Summary,
1166 "omega = {omega:?}\nphi = {phi:?}"
1167 );
1168 crate::verbosity_log!(
1169 crate::diag::Verbosity::Summary,
1170 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1171 obj_case1[0],
1172 obj_case1[1]
1173 );
1174 crate::verbosity_log!(
1175 crate::diag::Verbosity::Summary,
1176 "Case 2: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1177 obj_case2[0],
1178 obj_case2[1]
1179 );
1180 crate::verbosity_log!(
1181 crate::diag::Verbosity::Summary,
1182 "Interesting... Cases 1 and 2"
1183 );
1184 }
1185
1186 let obj_case3 = [
1188 CoppObjective::Time(1.0),
1189 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1190 ];
1191 copp2_problem.objectives = &obj_case3;
1192 let start = Instant::now();
1193 let mut a_case3 = copp2_socp(&copp2_problem, &options_socp)?;
1194 let tc_copp2_case3 = start.elapsed().as_secs_f64() * 1E3;
1195 robot
1196 .constraints
1197 .project_to_feasible_topp2(&mut a_case3, &a_feasible, 0)?;
1198 let (_, obj_case3) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case3);
1199 if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1200 crate::verbosity_log!(
1201 crate::diag::Verbosity::Summary,
1202 "omega = {omega:?}\nphi = {phi:?}"
1203 );
1204 crate::verbosity_log!(
1205 crate::diag::Verbosity::Summary,
1206 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1207 obj_case1[0],
1208 obj_case1[1],
1209 obj_case1[2]
1210 );
1211 crate::verbosity_log!(
1212 crate::diag::Verbosity::Summary,
1213 "Case 3: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1214 obj_case3[0],
1215 obj_case3[1],
1216 obj_case3[2]
1217 );
1218 crate::verbosity_log!(
1219 crate::diag::Verbosity::Summary,
1220 "Interesting... Cases 1 and 3"
1221 );
1222 }
1223
1224 let obj_case4 = [
1226 CoppObjective::Time(1.0),
1227 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1228 ];
1229 copp2_problem.objectives = &obj_case4;
1230 let start = Instant::now();
1231 let mut a_case4 = copp2_socp(&copp2_problem, &options_socp)?;
1232 let tc_copp2_case4 = start.elapsed().as_secs_f64() * 1E3;
1233 robot
1234 .constraints
1235 .project_to_feasible_topp2(&mut a_case4, &a_feasible, 0)?;
1236 let (_, obj_case4) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case4);
1237 if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
1238 crate::verbosity_log!(
1239 crate::diag::Verbosity::Summary,
1240 "omega = {omega:?}\nphi = {phi:?}"
1241 );
1242 crate::verbosity_log!(
1243 crate::diag::Verbosity::Summary,
1244 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1245 obj_case1[0],
1246 obj_case1[1],
1247 obj_case1[3]
1248 );
1249 crate::verbosity_log!(
1250 crate::diag::Verbosity::Summary,
1251 "Case 4: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1252 obj_case4[0],
1253 obj_case4[1],
1254 obj_case4[3]
1255 );
1256 crate::verbosity_log!(
1257 crate::diag::Verbosity::Summary,
1258 "Interesting... Cases 1 and 4"
1259 );
1260 }
1261 if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
1262 crate::verbosity_log!(
1263 crate::diag::Verbosity::Summary,
1264 "omega = {omega:?}\nphi = {phi:?}"
1265 );
1266 crate::verbosity_log!(
1267 crate::diag::Verbosity::Summary,
1268 "Case 2: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1269 obj_case2[0],
1270 obj_case2[2],
1271 obj_case2[3]
1272 );
1273 crate::verbosity_log!(
1274 crate::diag::Verbosity::Summary,
1275 "Case 4: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1276 obj_case4[0],
1277 obj_case4[2],
1278 obj_case4[3]
1279 );
1280 crate::verbosity_log!(
1281 crate::diag::Verbosity::Summary,
1282 "Interesting... Cases 2 and 4"
1283 );
1284 }
1285
1286 if flag_print_step {
1287 crate::verbosity_log!(
1288 crate::diag::Verbosity::Summary,
1289 "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1290 i_exp + 1,
1291 tc_copp2_case0,
1292 obj_case0,
1293 tc_copp2_case1,
1294 obj_case1,
1295 tc_copp2_case2,
1296 obj_case2,
1297 tc_copp2_case3,
1298 obj_case3,
1299 tc_copp2_case4,
1300 obj_case4
1301 );
1302 }
1303
1304 tc_sum_case0 += tc_copp2_case0;
1305 tc_sum_case1 += tc_copp2_case1;
1306 tc_sum_case2 += tc_copp2_case2;
1307 tc_sum_case3 += tc_copp2_case3;
1308 tc_sum_case4 += tc_copp2_case4;
1309 for i in 0..obj_case0.len() {
1310 obj_sum_case0[i] += obj_case0[i];
1311 obj_sum_case1[i] += obj_case1[i];
1312 obj_sum_case2[i] += obj_case2[i];
1313 obj_sum_case3[i] += obj_case3[i];
1314 obj_sum_case4[i] += obj_case4[i];
1315 }
1316 }
1317
1318 for i in 0..4 {
1319 obj_sum_case0[i] /= n_exp as f64;
1320 obj_sum_case1[i] /= n_exp as f64;
1321 obj_sum_case2[i] /= n_exp as f64;
1322 obj_sum_case3[i] /= n_exp as f64;
1323 obj_sum_case4[i] /= n_exp as f64;
1324 }
1325
1326 crate::verbosity_log!(
1327 crate::diag::Verbosity::Summary,
1328 "Average {} experiments:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1329 n_exp,
1330 tc_sum_case0 / n_exp as f64,
1331 obj_sum_case0,
1332 tc_sum_case1 / n_exp as f64,
1333 obj_sum_case1,
1334 tc_sum_case2 / n_exp as f64,
1335 obj_sum_case2,
1336 tc_sum_case3 / n_exp as f64,
1337 obj_sum_case3,
1338 tc_sum_case4 / n_exp as f64,
1339 obj_sum_case4
1340 );
1341
1342 Ok(())
1343 }
1344
1345 fn run_test_copp2_socp_once(_options_socp: &ClarabelOptions) -> Result<(), CoppError> {
1346 run_test_copp2_socp_repeated(1, false)
1347 }
1348
1349 fn run_one_copp2_socp_only_time_case(
1350 options_ra: &ReachSet2Options,
1351 options_socp: &ClarabelOptions,
1352 ) -> Result<(f64, f64, f64, f64, f64, f64), CoppError> {
1353 let n: usize = 1000;
1354 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1355 let dim = robot.dim();
1356
1357 let mut rng = rand::rng();
1358 let (s, path, omega, phi) =
1359 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1360 robot
1361 .with_s(&s.as_view())?
1362 .with_q_from_path_2nd(&path, 0, n)?;
1363 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1364
1365 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1366 let start = Instant::now();
1367 let a_ra = topp2_ra(&topp2_problem, options_ra)?;
1368 let tc_ra = start.elapsed().as_secs_f64() * 1E3;
1369 let (tf_ra, _) = s_to_t_topp2(s.as_slice(), &a_ra, 0.0)?;
1370
1371 let obj1 = [CoppObjective::Linear(
1372 1.0,
1373 &vec![-1.0; n],
1374 &vec![0.0; n - 1],
1375 )];
1376 let mut copp2_problem =
1377 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &obj1).build()?;
1378 let start = Instant::now();
1379 let a_lp = copp2_socp(&copp2_problem, options_socp)?;
1380 let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1381 let (tf_lp, _) = s_to_t_topp2(s.as_slice(), &a_lp, 0.0)?;
1382
1383 copp2_problem.objectives = &[CoppObjective::Time(1.0)];
1384 let start = Instant::now();
1385 let a_qp = copp2_socp(&copp2_problem, options_socp)?;
1386 let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1387 let (tf_qp, _) = s_to_t_topp2(s.as_slice(), &a_qp, 0.0)?;
1388
1389 if (tf_lp - tf_ra).abs() > 1e-3 || (tf_qp - tf_ra).abs() > 1e-3 {
1390 crate::verbosity_log!(
1391 crate::diag::Verbosity::Summary,
1392 "omega = {omega:?}\nphi = {phi:?}"
1393 );
1394 panic!("COPP2 time optimality failed!");
1395 }
1396
1397 Ok((tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp))
1398 }
1399
1400 fn run_test_copp2_socp_only_time_repeated(
1401 n_exp: usize,
1402 flag_print_step: bool,
1403 ) -> Result<(), CoppError> {
1404 let mut tc_sum_ra = 0.0;
1405 let mut tc_sum_lp = 0.0;
1406 let mut tc_sum_qp = 0.0;
1407 let mut tf_sum_ra = 0.0;
1408 let mut tf_sum_lp = 0.0;
1409 let mut tf_sum_qp = 0.0;
1410
1411 let options_ra = ReachSet2OptionsBuilder::new()
1412 .lp_feas_tol(1E-9)
1413 .a_cmp_abs_tol(1E-9)
1414 .a_cmp_rel_tol(1E-9)
1415 .build()?;
1416 let options_socp = ClarabelOptionsBuilder::new()
1417 .allow_almost_solved(true)
1418 .build()?;
1419
1420 for i_exp in 0..n_exp {
1421 let (tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp) =
1422 run_one_copp2_socp_only_time_case(&options_ra, &options_socp)?;
1423
1424 if flag_print_step {
1425 crate::verbosity_log!(
1426 crate::diag::Verbosity::Summary,
1427 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1428 i_exp + 1,
1429 tc_ra,
1430 tc_lp,
1431 tc_qp,
1432 tf_ra,
1433 tf_lp,
1434 tf_qp,
1435 );
1436 }
1437
1438 tc_sum_ra += tc_ra;
1439 tc_sum_lp += tc_lp;
1440 tc_sum_qp += tc_qp;
1441 tf_sum_ra += tf_ra;
1442 tf_sum_lp += tf_lp;
1443 tf_sum_qp += tf_qp;
1444 }
1445
1446 crate::verbosity_log!(
1447 crate::diag::Verbosity::Summary,
1448 "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1449 n_exp,
1450 tc_sum_ra / n_exp as f64,
1451 tc_sum_lp / n_exp as f64,
1452 tc_sum_qp / n_exp as f64,
1453 tf_sum_ra / n_exp as f64,
1454 tf_sum_lp / n_exp as f64,
1455 tf_sum_qp / n_exp as f64
1456 );
1457
1458 Ok(())
1459 }
1460}